package com.m4f.utils.i18n.dao.impl.jdo;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Logger;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import com.google.appengine.api.datastore.Category;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.m4f.business.domain.BaseEntity;
import com.m4f.business.domain.ifc.Taggeable;
import com.m4f.business.persistence.PMF;
import com.m4f.utils.dao.GaeFilter;
import com.m4f.utils.i18n.annotations.DeleteMultilanguage;
import com.m4f.utils.i18n.dao.ifc.I18nDAOSupport;

public class JdoI18nDAO implements I18nDAOSupport {
	
	private static final Logger LOGGER = Logger.getLogger(JdoI18nDAO.class.getName());
	
	public <T extends BaseEntity> T createInstance(Class<T> clazz) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		return pm.newInstance(clazz);
	}
	
	public <T extends BaseEntity> List<Long> getAllIds(Class<T> clazz, String ordering, String filter, String params, Object[] values) throws Exception {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query q = null;
		if(filter != null && !("").equals(filter)) {
			q = pm.newQuery("select id from " + clazz.getName() + " where " + filter);
			q.declareParameters(params);
		} else {
			q = pm.newQuery("select id from " + clazz.getName());
		}
		 
		this.setOrdering(q, ordering);
		List<Long> ids = null;
		
		if(filter != null && !("").equals(filter)) {
			ids = (List<Long>) q.executeWithArray(values);
		} else {
			ids = (List<Long>) q.execute();
		}
		
		 return ids;
	}
	
	public <T extends BaseEntity> List<Long> getAllIds(Class<T> clazz, String ordering, String filter, String params, Object[] values, int init, int end) throws Exception {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query q = null;
		if(filter != null && !("").equals(filter)) {
			q = pm.newQuery("select id from " + clazz.getName(), filter);
			q.declareParameters(params);
		} else {
			q = pm.newQuery("select id from " + clazz.getName());
		}
		 
		this.setOrdering(q, ordering);
		if((init<end) && (init>=0)) {
			q.setRange(init, end);
		}
		
		List<Long> ids = null;
		
		if(filter != null && !("").equals(filter)) {
			ids = (List<Long>) q.executeWithArray(values);
		} else {
			ids = (List<Long>) q.execute();
		}
		
		 return ids;
	}
	
	
	
	public void saveOrUpdate(BaseEntity entity, Locale locale) throws Exception {
		PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
        	pm.makePersistent(entity);
        } catch(Exception e) {
        	throw e;
        } finally {
        	pm.flush();
            pm.close();
        }
	}
	
	public <T extends BaseEntity> void saveOrUpdateCollection(Collection<T> entities, Locale locale) throws Exception {
		PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
        	pm.makePersistentAll(entities);
        } catch(Exception e) {
        	throw e;
        } finally {
        	pm.flush();
            pm.close();
        }
	}
	
	@DeleteMultilanguage
	public void delete(BaseEntity entity, Locale locale) throws Exception {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			pm.deletePersistent(entity);
		} catch(Exception e) {
	    	throw e;
	    } finally {
	    	pm.flush();
	    	pm.close();
	    }
	}
	
	public <T extends BaseEntity> void delete(Collection<T> objs, Locale locale) throws Exception {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			pm.deletePersistentAll(objs);
		} catch(Exception e) {
	    	throw e;
	    } finally {
	    	pm.flush();
	    	pm.close();
	    }
	}
	
	public <T extends BaseEntity> void erasure(Class<T> clazz) throws Exception {
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		// The Query interface assembles a query
		com.google.appengine.api.datastore.Query q = 
			new com.google.appengine.api.datastore.Query(clazz.getSimpleName());
		PreparedQuery pq = datastore.prepare(q);
		for (Entity result : pq.asIterable()) {
			datastore.delete(result.getKey());
		}
	}
	
	//@LoadMultilanguage
	public <T extends BaseEntity> T findById(Class<T> clazz, Locale locale, Long id) throws Exception {	
		PersistenceManager pm = PMF.get().getPersistenceManager();
		T obj = null, detached = null;
		try {
			obj = pm.getObjectById(clazz, id);
			detached = pm.detachCopy(obj);
		} catch(Exception e) {
			throw e;
		} finally {
	        pm.close();
	    }
		return detached;
	}

	
	@SuppressWarnings("unchecked")
	public <T extends BaseEntity> List<T> findAll(Class<T> clazz, Locale locale, String ordering) throws Exception {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(clazz);
		this.setOrdering(query, ordering);
		List<T> results = new ArrayList<T>(), detached = new ArrayList<T>();
		try {
	        results.addAll((List<T>)query.execute());   
	        detached = (List<T>)pm.detachCopyAll(results);  
	    } catch(Exception e) {
	    	throw e;
	    } finally {
	        query.closeAll();
	        pm.close();
	    }
	    return detached;
	}
		
	 /**
	 * Find an entity of a given class using a filter.
	 * @param <T> type of entity to look up.
	 * @param entityClass class of entity to be found.
	 * @param filter (JDOQL) filter to apply to entity lookup.
	 * @param params declarations of parameters in filter string.
	 * @param values values of parameters (in order of appearance in
	 * 	'params' string).
	 * @return entity or <code>null</code> if no entity of the provided class
	 * 	exists matching the provided filter. 
	 */
	public <T extends BaseEntity> T findEntity(Class<T> entityClass, Locale locale,
			String filter,	String params,	Object[] values)	{
		Collection<T> entities =
			findEntities(entityClass, locale, filter, params, values, null);
		if (entities.isEmpty())
			return null;		
		return entities.iterator().next();
	}
	
	/**
	 * Find all entities of a given class in a given order, matching a filter.
	 * @param <T> type of entity to look up.
	 * @param entityClass class of entities to be found.
	 * @param filter (JDOQL) filter to apply to entity lookup.
	 * @param params declarations of parameters in filter string.
	 * @param values values of parameters (in order of appearance in
	 * 	'params' string).
	 * @param ordering ordering to be applied to results (can be <code>null</code>).
	 * @return entity or <code>null</code> if no entity of the provided class
	 * 	exists matching the provided filter. 
	 */
	@SuppressWarnings("unchecked")
	public <T extends BaseEntity> Collection<T> findEntities(
			Class<T> entityClass, Locale locale, String filter, String params,
			Object[] values, String ordering) {
		// construct query
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(entityClass, filter);
		query.declareParameters(params);
		this.setOrdering(query, ordering);
		// execute query
		Collection<T> entities = (Collection<T>) query.executeWithArray(values);
		Collection<T> detached = pm.detachCopyAll(entities);
		pm.close();
		return detached;
	}
	
	public <T extends BaseEntity> long count(Class<T> entityClass, 
			Map<String, Object> propertyMap) {
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		// The Query interface assembles a query
		com.google.appengine.api.datastore.Query q = 
			new com.google.appengine.api.datastore.Query(entityClass.getSimpleName());
		for(String propertyName : propertyMap.keySet()) {
			q.addFilter(propertyName, 
					com.google.appengine.api.datastore.Query.FilterOperator.EQUAL, 
					propertyMap.get(propertyName));
		}
		PreparedQuery pq = datastore.prepare(q);
		return pq.countEntities(FetchOptions.Builder.withLimit(100000));
	}
	
	public <T extends BaseEntity> long count(Class<T> entityClass, List<GaeFilter> filters) {
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		// The Query interface assembles a query
		com.google.appengine.api.datastore.Query q = 
			new com.google.appengine.api.datastore.Query(entityClass.getSimpleName());
		if(filters != null) {
			for(GaeFilter filter : filters) {
				q.addFilter(filter.getField(), filter.getOperator(), filter.getValue());
			}
		}
		PreparedQuery pq = datastore.prepare(q);
		return pq.countEntities(FetchOptions.Builder.withLimit(100000));
	}
	
	public <T extends BaseEntity> long count(Class<T> entityClass) {
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		// The Query interface assembles a query
		com.google.appengine.api.datastore.Query q = 
			new com.google.appengine.api.datastore.Query(entityClass.getSimpleName());
		PreparedQuery pq = datastore.prepare(q);	
		return pq.countEntities(FetchOptions.Builder.withLimit(100000));
	}
	
	@SuppressWarnings("unchecked")
	public <T extends BaseEntity> Collection<T> findEntitiesByRange(Class<T> entityClass, 
			Locale locale, int init, int end, String ordering) {
		// construct query
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(entityClass);
		this.setOrdering(query, ordering);
		if((init<end) && (init>=0)) {
			query.setRange(init, end);
		}
		// execute query
		Collection<T> entities = (Collection<T>) query.execute();
		Collection<T> detached = pm.detachCopyAll(entities);
		pm.close();
		return detached;
	}

	@Override
	public <T extends BaseEntity> Collection<T> findEntitiesByRange(
			Class<T> entityClass, Locale locale, String filter, String params,
			Object[] values, int init, int end, String ordering) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(entityClass, filter);
		query.declareParameters(params);
		this.setOrdering(query, ordering);
		if((init<end) && (init>=0)) {
			query.setRange(init, end);
		}
		// execute query
		Collection<T> entities = (Collection<T>) query.executeWithArray(values);
		//LOGGER.info("## Entidades size: " + entities.size());
		Collection<T> detached = pm.detachCopyAll(entities);
		pm.close();
		return detached;
	}
	
	@Override
	public Collection<Category> getCategories(Class<? extends Taggeable> clazz, String fieldName, Locale locale) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		StringBuffer sb = new StringBuffer("select ").append(fieldName).append(" from ").append(clazz.getName());
		Query q = pm.newQuery(sb.toString());
		Collection<Category> tags = (Collection<Category>) q.execute(); 
		//Collection<Category> detached = pm.detachCopyAll(tags);
		q.closeAll();
	    return tags;
	}
	
	@Override
	public <T extends BaseEntity> Collection<T> findEntitiesByIds(Class<T> entityClass, Locale locale, 
			String idField, List<Long> ids, int init, int end, String ordering) throws Exception {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		//StringBuffer querySb = 
			new StringBuffer("select from ").append(entityClass.getName()).append(" where ").append(":ids.contains(").append(idField).append(")");
		//Query query = pm.newQuery(querySb.toString());
		//query.declareParameters(params);
		
		Collection<T> objs = new ArrayList<T>();
		Query query = pm.newQuery(entityClass,":ids.contains(" + idField + ")");
		if((ids!=null)&&(ids.size()>0)) {
			 objs.addAll((Collection<T>)query.execute(ids));
		}
		 
		this.setOrdering(query, ordering);
		if((init<end) && (init>=0)) {
			query.setRange(init, end);
		}
		// execute query
		//Collection<T> entities = (Collection<T>) query.execute(ids);
		Collection<T> detached = pm.detachCopyAll(objs);
		pm.close();
		return detached;
	}
	
	public <T extends BaseEntity> long countByIds(Class<T> entityClass, String idField, List<Long> ids) {
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		// The Query interface assembles a query
		com.google.appengine.api.datastore.Query q = 
			new com.google.appengine.api.datastore.Query(entityClass.getSimpleName());
		q.addFilter(idField, com.google.appengine.api.datastore.Query.FilterOperator.IN, ids);
		PreparedQuery pq = datastore.prepare(q);
		return pq.countEntities(FetchOptions.Builder.withLimit(100000));
	}
	
	private void setOrdering(Query query, String ordering) {
		if (ordering != null && !("").equals(ordering)){
			StringBuffer sb = new StringBuffer();
			String[] orderParams = ordering.split("[,]");
			for(String param : orderParams) {
				if(!param.startsWith("-")) sb.append(param).append(" ascending");
				else sb.append(param.substring(1)).append(" descending");
				sb.append(",");
			}
			sb.deleteCharAt(sb.length()-1); // Removes the last comma
			query.setOrdering(sb.toString());		
		}
	}
}